/*
 * Copyright 1999-2011 Alibaba Group.
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.sgzn.sgpay.util;

import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * ClassUtils .
 * <p/>
 * Created by meast on 2014/5/9.
 */
@Slf4j
public class ClassUtils {
    private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
    /**
     * void(V).
     */
    public static final char JVM_VOID = 'V';

    /**
     * boolean(Z).
     */
    public static final char JVM_BOOLEAN = 'Z';

    /**
     * byte(B).
     */
    public static final char JVM_BYTE = 'B';

    /**
     * char(C).
     */
    public static final char JVM_CHAR = 'C';

    /**
     * double(D).
     */
    public static final char JVM_DOUBLE = 'D';

    /**
     * float(F).
     */
    public static final char JVM_FLOAT = 'F';

    /**
     * int(I).
     */
    public static final char JVM_INT = 'I';

    /**
     * long(J).
     */
    public static final char JVM_LONG = 'J';

    /**
     * short(S).
     */
    public static final char JVM_SHORT = 'S';


    private static final ConcurrentMap<String, Class<?>> NAME_CLASS_CACHE = new ConcurrentHashMap<String, Class<?>>();

    /**
     * 判断是否有无参构造函数
     */
    public static boolean createNewInstanceNoParam(Class<?> cl) {
        boolean match = false;

        //取得全部的构造函数
        Constructor<?> cons[] = cl.getConstructors();
        for (Constructor constructor : cons) {
            Class<?>[] types = constructor.getParameterTypes();
            if (types.length == 0) {
                match = true;
            }
        }
        return match;
    }

    public static Object newInstance(String name) {
        try {
            return forName(name).newInstance();
        } catch (InstantiationException e) {
            throw new IllegalStateException(e.getMessage(), e);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }


    public static Class<?> forName(String className) {
        try {
            return forNameReal(className);
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

    public static Class<?> forNameReal(String className) throws ClassNotFoundException {
        switch (className) {
            case "boolean":
                return boolean.class;
            case "byte":
                return byte.class;
            case "char":
                return char.class;
            case "short":
                return short.class;
            case "int":
                return int.class;
            case "long":
                return long.class;
            case "float":
                return float.class;
            case "double":
                return double.class;
            case "boolean[]":
                return boolean[].class;
            case "byte[]":
                return byte[].class;
            case "char[]":
                return char[].class;
            case "short[]":
                return short[].class;
            case "int[]":
                return int[].class;
            case "long[]":
                return long[].class;
            case "float[]":
                return float[].class;
            case "double[]":
                return double[].class;
            default:
                try {
                    return arrayForName(className);
                } catch (ClassNotFoundException e) {
                    if (className.indexOf('.') == -1) {
                        try {
                            return arrayForName("java.lang." + className);
                        } catch (ClassNotFoundException ignored) {

                        }
                    }
                    throw e;
                }
        }
    }

    private static Class<?> arrayForName(String className) throws ClassNotFoundException {
        return Class.forName(className.endsWith("[]")
                ? "[L" + className.substring(0, className.length() - 2) + ";"
                : className, true, Thread.currentThread().getContextClassLoader());
    }

    /**
     * name to class.
     * "boolean" => boolean.class
     * "java.com.bestpay.fas.util.Map[][]" => java.com.bestpay.fas.util.Map[][].class
     *
     * @param name name.
     * @return Class instance.
     */
    public static Class<?> name2class(String name) throws ClassNotFoundException {
        return name2class(getClassLoader(ClassUtils.class), name);
    }

    /**
     * name to class.
     * "boolean" => boolean.class
     * "java.com.bestpay.fas.util.Map[][]" => java.com.bestpay.fas.util.Map[][].class
     *
     * @param cl   ClassLoader instance.
     * @param name name.
     * @return Class instance.
     */
    private static Class<?> name2class(ClassLoader cl, String name) throws ClassNotFoundException {
        int c = 0, index = name.indexOf('[');
        if (index > 0) {
            c = (name.length() - index) / 2;
            name = name.substring(0, index);
        }
        if (c > 0) {
            StringBuilder sb = new StringBuilder();
            while (c-- > 0) {
                sb.append("[");
            }

            switch (name) {
                case "void":
                    sb.append(JVM_VOID);
                    break;
                case "boolean":
                    sb.append(JVM_BOOLEAN);
                    break;
                case "byte":
                    sb.append(JVM_BYTE);
                    break;
                case "char":
                    sb.append(JVM_CHAR);
                    break;
                case "double":
                    sb.append(JVM_DOUBLE);
                    break;
                case "float":
                    sb.append(JVM_FLOAT);
                    break;
                case "int":
                    sb.append(JVM_INT);
                    break;
                case "long":
                    sb.append(JVM_LONG);
                    break;
                case "short":
                    sb.append(JVM_SHORT);
                    break;
                default:
                    sb.append('L').append(name).append(';'); // "java.lang.Object" ==> "Ljava.lang.Object;"
                    break;
            }
            name = sb.toString();
        } else {
            switch (name) {
                case "void":
                    return void.class;
                case "boolean":
                    return boolean.class;
                case "byte":
                    return byte.class;
                case "char":
                    return char.class;
                case "double":
                    return double.class;
                case "float":
                    return float.class;
                case "int":
                    return int.class;
                case "long":
                    return long.class;
                case "short":
                    return short.class;
            }
        }

        if (cl == null) {
            cl = getClassLoader(ClassUtils.class);
        }
        Class<?> clazz = NAME_CLASS_CACHE.get(name);
        if (clazz == null) {
            clazz = Class.forName(name, true, cl);
            NAME_CLASS_CACHE.put(name, clazz);
        }
        return clazz;
    }

    public static ClassLoader getClassLoader(Class<?> cls) {
        ClassLoader cl = null;
        try {
            cl = Thread.currentThread().getContextClassLoader();
        } catch (Exception ex) {
            // Cannot access thread context ClassLoader - falling back to system class loader...
        }
        if (cl == null) {
            // No thread context class loader -> use class loader of this class.
            cl = cls.getClassLoader();
        }
        return cl;
    }

    public static boolean isPrimitive(Class<?> type) {
        return type.isPrimitive()
                || type == String.class
                || type == Character.class
                || type == Boolean.class
                || type == Byte.class
                || type == Short.class
                || type == Integer.class
                || type == Long.class
                || type == Float.class
                || type == Double.class
                || type == Date.class
                || type == Object.class;
    }

    private ClassUtils() {
    }

    public static Object createInstance(Class<?> type) {
        if (type.isArray()) {
            Object array = Array.newInstance(type.getComponentType());
            return array;
        } else if (type.isInterface()) {
            try {
                Collection result = (Collection) type.newInstance();
                return result;
            } catch (Exception e) {
            }
        } else if (type == List.class) {
            return new ArrayList<Object>();
        } else if (type == Set.class) {
            return new HashSet<Object>();
        } else if (type.isEnum()) {
            return "";
        } else {
            try {
                return type.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                log.error("create instance of [{}] happened exception, detail :[{}].", type.getSimpleName(), e);
            }
        }
        return null;
    }

    public static List<Class<?>> getClasses(Class<?> testClass) {
        ArrayList<Class<?>> results = new ArrayList<Class<?>>();
        Class<?> current = testClass;
        while (current != null) {
            results.add(current);
            current = current.getSuperclass();
        }
        return results;
    }
}
